Http Server 驱动
该驱动会启动一个 http server
, 允许每个 表
注册一个 路径
. 客户端通过不同的 路径
向不同的表推送数据.
驱动配置说明
- 请求路径: 定义当前表接收数据的访问路径, 可以包含多层级的路径. 例如:
/data
,/api/data
等, 也可以使用占位符/api/data/{deviceId}
, 占位符的值可以从request.pathVariables
获取. - 前缀匹配: 启用后, 可接收所有请求路径以该路径为前缀的请求. 例如:
/api/data
, 可以接收/api/data
,/api/data/1
,/api/data/device/设备编号1
等请求. - 请求方式: 即接收哪些请求方式, 可多选.
- 是否注册不带前缀的请求路径: 不勾选该选项时, 驱动默认创建的接口路径默认带有
/http-server-driver
前缀. 例如, 设置的 请求路径 为/device/data
, 实际访问路径为/http-server-driver/device/data
. 如果勾选了该选项后, 会同时注册/http-server-driver/device/data
和/device/data
两个访问路径. 可通过http://平台IP:3030/http-server-driver/device/data
或http://平台IP:驱动端口/http-server-driver/device/data
或http://平台IP:驱动端口/device/data
方式访问.
注: 通过平台
3030
或31000
端口访问http server
驱动接口时, 必须添加/http-server-driver
前缀. 如果平台为多项目版本时, 必须在请求头中添加x-request-project
, 值为该驱动实例所在项目的ID
.
脚本说明
脚本语言: JavasScript ECMAScript 5.1
驱动使用时要求提供 数据处理脚本
脚本函数来处理接收客户端推送过来的数据.
除此之外, 还内置了 lodash
, crypto-js
, moment
, xml-js
和 formulajs(Excel函数)
包.
注: 所有的脚本的函数名必须为
handler
.
表上下文
每个表有一个独立的上下文对象, 用于在脚本中存储一些数据, 这些数据会在该表的所有脚本中共享. 上下文对象提供以下方法:
put
用于向上下文中存储数据.
注: 存入的数据需要自行清理, 否则可能导致
OOM
等问题.
参数说明
参数名 | 参数类型 | 参数说明 |
---|---|---|
key | string | 数据项的 key |
value | any | 数据项的值 |
返回值
无
示例
function handler(context, request) {
// 向上下文中存储一个字符串
context.put("string", "this is a string");
// 向上下文中存储一个数值
context.put("number", 3.141);
// 向上下文中存储一个对象
context.put("object", {name: "张三", age: 18});
}
containsKey
判断上下文中是否存在指定的 key
参数说明
参数名 | 参数类型 | 参数说明 |
---|---|---|
key | string | 数据项的 key |
返回值
bool
. true
表示 key
存在, false
表示 key
不存在
示例
function handler(context, request) {
// 向上下文中存储一个字符串
context.put("string", "this is a string");
// 返回 true
context.containsKey("string");
// 返回 false
context.containsKey("string1");
}
get
从上下文中获取指定的 key
对应的数据. 如果 key
不存在则返回 undefined
参数说明
参数名 | 参数类型 | 参数说明 |
---|---|---|
key | string | 数据项的 key |
返回值
any
或 undefined
. 返回 put
时写入的数据.
示例
function handler(context, request) {
// 向上下文中存储一个字符串
context.put("string", "this is a string");
// 返回 "this is a string"
context.get("string");
// 返回 undefined
context.containsKey("string1");
}
getAndRemove
从上下文中获取指定的 key
对应的数据并且在返回后 删除 该 key
. 如果 key
不存在则返回 undefined
.
注: 该函数返回后, 再使用
get
或getAndRemove
均返回undefined
.
参数说明
参数名 | 参数类型 | 参数说明 |
---|---|---|
key | string | 数据项的 key |
返回值
any
或 undefined
. 返回 put
时写入的数据.
示例
function handler(context, request) {
// 向上下文中存储一个字符串
context.put("string", "this is a string");
// 返回 "this is a string"
context.getAndRemove("string");
// 返回 undefined
context.get("string");
// 返回 undefined
context.getAndRemove("string");
}
remove
从上下文中删除指定的 key
, 如果 key
不存在则不执行任何操作.
参数说明
参数名 | 参数类型 | 参数说明 |
---|---|---|
key | string | 数据项的 key |
返回值
无
示例
function handler(context, request) {
// 向上下文中存储一个字符串
context.put("string", "this is a string");
// 数据被删除
context.remove("string");
// 不执行任何操作
context.remove("string");
}
reportCommand
上传指令执行结果. 有些场景指令的执行结果是异步通知的, 或者指令通知与实时数据上传为同一接口. 此时,
可以通过 reportCommand
上报指令执行结果.
参数说明
参数名 | 参数类型 | 参数说明 |
---|---|---|
serialNo | string | 平台指令序号, 每次下发指令的序号不同 |
state | string | 指令最终执行状态. 可取值为 success , failure 和 pending 分别代表执行成功 , 失败 和 未完成 |
result | string | 指令执行结果或失败说明 |
返回值
无
示例
function handler(context, request) {
// 向平台上报指令 "cmd_001" 执行结果, 状态为 "success", 结果为 "成功"
context.reportCommand("cmd_001", "success", "成功");
// 向平台上报指令 "cmd_001" 执行结果, 状态为 "failure", 失败原因为 "设备不可用"
context.reportCommand("cmd_001", "failure", "设备不可用");
}
数据处理脚本
该脚本用于处理从客户端发送的数据, 然后将数据解析为平台规定的格式并返回.
函数定义如下:
/**
* /**
* 数据处理脚本, 用于解析客户端推送过来的数据
* @param {Object} context 表上下文
* @param {Object} request 请求对象, 可以获取请求的相关信息
* @returns {{result: [{values: {}, id: string, time: number}], headers: {}, message: string, statusCode: number}}
*/
function handler(context, request) {
// 数据包处理逻辑
// statusCode 返回给客户端的状态码
// message 返回给客户端的响应数据
// headers 响应头
// result 从请求中解析出来的数据, 要求必须为数组. 如果请求不是上报实时采集数据时, 返回空数组即可.
return {
"statusCode": 200,
"message": "ok",
"headers": {"Content-Type": "application/json"},
"result": [{
"id": deviceId,
"time": timestamp,
"values": {"SignalPower": SignalPower, "SNR": SNR, "TxPower": TxPower}
}]
};
}
参数说明
参数名 | 参数类型 | 参数说明 |
---|---|---|
context | object | 表上下文 |
request | object | 请求对象 |
url | string | 请求 url |
method | string | 请求方式, 例如: GET, POST, PUT 等 |
remoteAddr | string | 客户端地址 |
path | string | 请求路径 |
headers | object | 请求头信息. 注: 值为字符串数组 |
cookies | object | cookie 信息. 注: 值为字符串数组 |
pathVariables | object | 路径参数. 注: 值为字符串数组 |
query | object | url 中的请求参数. 注: 值为字符串数组 |
rawQuery | string | url 中参数部分内容 |
body | string | 请求体 |
示例
以 curl
命令请求为例, 说明 request
中各字段的含义, 表注册的 path
为 /demo/{deviceId}
curl -XPOST -H "x-request-project:62c4f8aaa0f974f96cca7ddc" -H "Content-Type:application/json" -d '{"deviceId":"2d1f1a708b5d4cef880937d67b5e5842","IMEI":"","IMSI":"","deviceType":"","tenantId":"1","productId":"1503","messageType":"dataReport","topic":"v1/up/ad","assocAssetId":"","timestamp":1528183784371,"payload":{"SignalPower":-792,"SNR":-55,"TxPower":50,"CellId":66966098,"Length":3,"Updata":"REVG"},"upPacketSN":-1,"upDataSN":-1,"serviceId":"","protocol":"tup"}' 'http://localhost:9505/demo/123456?a=123&b=abc'
字段名 | 数据类型 | 值 |
---|---|---|
url | string | /demo/123456?a=123&b=abc |
method | string | POST |
remoteAddr | string | [::1]:62298 |
path | string | /demo/123456 |
headers | object | {"Accept": ["/"],"Content-Length": ["368"],"Content-Type": ["application/json"],"Cookie": ["Idea-7de7665d=c0b98b36-3396-41dd-b3a2-84e996a7155c"],"User-Agent": ["curl/7.68.0"],"X-Request-Prooject ": ["62 c4f8aaa0f974f96cca7ddc "]} |
cookies | object | {"Idea-7de7665d":"c0b98b36-3396-41dd-b3a2-84e996a7155c"} |
pathVariables | object | {"deviceId":"123456"} |
query | object | {"a":["123"],"b":["abc"]} |
rawQuery | string | a=123&b=abc |
body | string | {"deviceId":"2d1f1a708b5d4cef880937d67b5e5842","IMEI":"","IMSI":"","deviceType":"","tenantId":"1","productId":"1503","messageType":"dataReport","topic":"v1/up/ad","assocAssetId":"","timestamp":1528183784371,"payload":{"SignalPower":-792,"SNR":-55,"TxPower":50,"CellId":66966098,"Length":3,"Updata":"REVG"},"upPacketSN":-1,"upDataSN":-1,"serviceId":"","protocol":"tup"} |
注: 部分请求头信息由
curl
命令自动添加.
返回值说明
参数名 | 参数类型 | 必填 | 参数说明 | 示例值 |
---|---|---|---|---|
statusCode | number | 是 | 返回给客户端的 http 响应码 | 200 |
message | string | 否 | 返回给客户端的响应内容 | "success" |
headers | object | 否 | 返回给客户端的响应头 | {"Content-Type": "application/json", "custom-header": ["value1", "value2"]} |
result | object[] | 否 | 在请求中解析得到的实时数据 | [{"id":"d01","time":1665999863637,"values":{"temperature":17.5,"humidity":35.7}}] |
.id | string | 是 | 资产编号或设备标识 | d01 |
.time | number | 是 | 时间戳(ms) | 1664256913000 |
.values | object | 是 | 数据点信息 | {"temperature":17.5,"humidity":35.7} |
注: 响应头的值可以为
string
或Array<string>
两种类型。
示例
function handler(context, request) {
// 以请求体内容为 json 格式为例, 例如: {"id":"d01","time":"2022-10-17 17:57:32","values":[{"name":"temperature","data":17.5},{"name":"humidity","data":35.7}]}
// 示例, 从表上下文中获取数据
const valueFromContext = context.get("id");
// 获取请求体的内容并转换为 json 对象
const jsonData = JSON.parse(request.body);
// 从对象中提取时间信息
const time = moment(jsonData.time, "YYYY-MM-DD HH:mm:ss");
// 将 values 字段解析为平台规定的格式 {"key1": value1, "key2": "value2", ...}
const values = {};
for (let i = 0; i < jsonData.values.length; i++) {
const value = jsonData.values[i];
values[value.name] = value.data;
}
// 响回给客户端的响应码为 200
// 响应内容为 success
// 从请求中解析得到的实时数据为 [{id: jsonData.id, values: values, time: time.valueOf()}]
return {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"message": "success",
"result": [{id: jsonData.id, values: values, time: time.valueOf()}]
};
}
指令处理脚本
HTTP Server
驱动是通过 http
请求实现指令下发. 当下发指令时, 驱动会将指令内容传递给 指令处理脚本
.
由脚本从指令中解析出要调用的目标 http server
以及传递哪些参数、请求头和请求体信息, 然后驱动会根据这些信息最终发起请求.
指令脚本中包含两个函数, 分别为 handler
和 callback
.
- handler 用于解析平台指令, 并转化为请求信息
- callback 用于处理平台发送指令请求后响应处理, 可实现从响应信息提取出指令执行结果上报给平台. **注:
callback
为可选函数,
未定义时直接将指令请求响应结果作为指令结果**
有些系统指令执行结果的反馈是异步通知的, 或者目标系统有自己的指令标识. 因此需要将目标系统中的指令标识与平台的指令序号建立映射关系,
或者在接收到异步通知时再将执行结果上报给平台.
此时, 需要使用 callback
函数和 表上下文
来实现.
指令处理流程:
- 手动触发指令下发
- 驱动接收到指令下发请求时, 会调用
handler
函数. - 根据
handler
返回的请求信息, 发起请求 - 如果定义了
callback
函数, 则将步骤3
中的响应信息交给callback
处理 - 上报平台指令处理结果
注: 需要在指令配置中的
数据写入
部分配置自定义表单, 然后在发送指令时填写发送内容, 在脚本中command
参数即为填写的内容.
函数定义如下:
/**
* 指令处理脚本, 将指令内容解析为 http 请求数据格式.
*
* @param {Object} context 表上下文
* @param {string} serialNo 平台指令序号, 每个指令都有唯一的序号
* @param {string} tableId 设备所属表的标识
* @param {string} deviceId 设备标识
* @param {Object} command 指令信息
* @return {Object} 请求信息
*/
function handler(context, serialNo, deviceId, command) {
// 解析指令内容, 并返回请求相关信息
return {
url: `http://192.168.100.123/command/${deviceId}?time=${new Date().getTime()}`,
method: 'POST',
headers: {
"Content-Type": "application/json"
},
options: {
"timeout": 15000,
},
body: "the request body"
};
}
/**
* 指令请求回调函数. 接收 handler 函数对应请求的响应信息, 并转换为平台指令响应结果
*
* @param {Object} context 表上下文
* @param {string} serialNo 平台指令序号, 每个指令都有唯一的序号
* @param {string} tableId 设备所属表的标识
* @param {string} deviceId 设备标识
* @param {Object} command 指令信息
* @returns {{result: string, state: string}}
*/
function callback(context, tableId, deviceId, serialNo, response) {
// 根据响应信息执行相关操作
return {state: "success", result: "指令返回结果"};
}
handler 参数说明
参数名 | 参数类型 | 参数说明 |
---|---|---|
context | object | 表上下文 |
serialNo | string | 平台指令序号, 每个指令都有唯一的序号 |
tableId | string | 设备所属表的标识 |
deviceId | string | 自定义设备标识 |
command | object | 指令信息, 格式如下 |
指令格式如下:
{
"name": "demo",
"showName": "示例指令",
"params": {
"demo": {
"productId": 100123,
"operator": "张三",
"ttl": 7200,
"payload": [
{
"key": "status",
"value": "1"
},
{
"key": "temperature",
"value": "26"
}
]
}
}
}
字段名 | 参数类型 | 参数说明 |
---|---|---|
name | 字符串 | 指令名称 |
showName | 字符串 | 指令显示名称 |
params | 对象 | 数据写入配置, 格式由 数据写入 的配置决定 |
defaultValue | 对象 | 数据写入配置中各字段的默认值 |
handler 返回值说明
字段名 | 参数类型 | 必填 | 参数说明 | 示例值 |
---|---|---|---|---|
url | string | 是 | 请求地址 | http://192.168.100.123:8080/command/sn10032?seq=000123&delay=0 |
method | string | 是 | 请求方式 | 可以为 GET , POST , PUT 等 |
headers | object | 否 | 请求头 | {"Content-Type": "application/json", "Custom-Header": ["value1", "value2"]} |
options | object | 否 | 配置项 | {"Content-Type": "application/json", "Custom-Header": ["value1", "value2"]} |
timeout | number | 否 | 请求超时(ms) | 单位: ms, 默认: 15000 |
options | object | 否 | 配置项 | {"Content-Type": "application/json", "Custom-Header": ["value1", "value2"]} |
body | string | object | 否 | 请求体 |
callback 参数说明
参数名 | 参数类型 | 参数说明 |
---|---|---|
context | object | 表上下文 |
serialNo | string | 平台指令序号, 每个指令都有唯一的序号 |
tableId | string | 设备所属表的标识 |
deviceId | string | 自定义设备标识 |
response | object | 响应信息, 格式如下 |
响应信息格式如下:
{
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": "消息体"
}
字段名 | 参数类型 | 参数说明 |
---|---|---|
statusCode | number | 响应码. 例如: 200 |
headers | object | 响应头. 例如: {"Content-Type": "application/json"} |
body | string | 消息体. 例如: {"code": 0, "message": "success"} |
callback 返回值说明
字段名 | 参数类型 | 必填 | 参数说明 | 示例值 |
---|---|---|---|---|
state | string | 是 | 指令执行状态 | 枚举值. success , failure , pending |
result | string | 是 | 指令执行结果 | 成功 |
callback state 枚举状态说明
值 | 说明 |
---|---|
success | 指令执行成功 |
failure | 指令执行失败 |
pending | 指令未完成. 用于异步反馈指令执行结果情况 |
示例1
该示例, 未定义 callback
函数. 驱动直接使用请求的响应结果作为执行结果.
如果响应码为 200
, 表示指令执行成功, 消息体作为指令执行结果.
function handler(context, serialNo, deviceId, command) {
const {productId, operator, ttl, payload} = command.params.demo;
const content = {};
for (let i = 0; i < payload.length; i++) {
const data = payload[i];
content[data.name] = data.value;
}
return {
url: "http://192.168.50.12:8080/command",
method: "POST",
headers: {
"Content-Type": "application/json"
},
options: {
"timeout": 15000
},
body: {
"deviceId": deviceId,
"productId": productId,
"operator": operator,
"ttl": ttl,
"content": {
payload: content
}
}
};
}
示例2
该示例, 使用 callback
从响应信息中提取指令执行结果信息.
function handler(context, serialNo, deviceId, command) {
const {productId, operator, ttl, payload} = command.params.demo;
const content = {};
for (let i = 0; i < payload.length; i++) {
const data = payload[i];
content[data.name] = data.value;
}
return {
url: "http://192.168.50.12:8080/command",
method: "POST",
headers: {
"Content-Type": "application/json"
},
options: {
"timeout": 15000
},
body: {
"deviceId": deviceId,
"productId": productId,
"operator": operator,
"ttl": ttl,
"content": {
payload: content
}
}
};
}
/**
* 从响应信息中提取指令执行结果.
*/
function callback(context, serialNo, deviceId, response) {
// 例如, 消息体内容为 {"code": 200, "message": "success"}
const {code, message} = JSON.parse(response.body);
return {state: code == 200 ? "success" : "failure", result: "message"};
}
示例3
该示例中, 假设指令执行结果为异步通知, 并且目标系统有独立的指令标识
function handler(context, serialNo, deviceId, command) {
const {productId, operator, ttl, payload} = command.params.demo;
const content = {};
for (let i = 0; i < payload.length; i++) {
const data = payload[i];
content[data.name] = data.value;
}
return {
url: "http://192.168.50.12:8080/command",
method: "POST",
headers: {
"Content-Type": "application/json"
},
options: {
"timeout": 15000
},
body: {
"deviceId": deviceId,
"productId": productId,
"operator": operator,
"ttl": ttl,
"content": {
payload: content
}
}
};
}
function callback(context, serialNo, deviceId, response) {
// 例如, 消息体内容为 {"commandId": "cmd_001"}
const {commandId} = JSON.parse(response.body);
// 从消息体中提取出目标系统的指令标识, 并与平台指令序号建立映射关系
context.put(commandId, serialNo);
// 返回 state 为 pending, 表示指令未完成
return {state: "pending", result: "处理中"};
}
/**
* 数据处理脚本. 接收到目标系统推送的指令执行结果. 此时处理未完成的指令并上报到平台
*/
function handler(context, request) {
// 根据请求中的一些信息判断该请求为指令执行结果通知请求
if (request.headers["type"] === "command") {
// 从请求消息体中提取出命令标识以及指令执行结果
// 例如, 消息体格式为 {"commandId": "cmd_001", "code": 200, "message": "success"}
const {commandId, code, message} = JSON.parse(request.body);
// 根据目标系统的指令标识获取平台指令序号
const serialNo = context.getAndRemove(commandId);
// 通过表上下文上报指令执行结果
context.reportCommand(serialNo, code === 200 ? "success" : "failure", message);
// 返回空对象
return {};
}
}